﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Solidi
{
	public static class DependencyBuilderExtensions
	{
		public static IDependencyBuilder As<TAs>(this IDependencyBuilder source)
		{
			return source.Coalesce(d => d.Coalesce(new DependencyKey(typeof(TAs), d.Key.Name)));
		}

		public static IDependencyBuilder Named(this IDependencyBuilder source, IComparable name)
		{
			return source.Coalesce(d => d.Coalesce(new DependencyKey(d.Key.Type, name)));
		}

		public static IDependencyBuilder Singleton(this IDependencyBuilder source)
		{
			return source.Coalesce(d => d.Coalesce(lifestyle: new SingletonLifestyle()));
		}

		public static IDependencyBuilder Transient(this IDependencyBuilder source)
		{
			return source.Coalesce(d => d.Coalesce(lifestyle: new TransientLifestyle()));
		}

		public static IDependencyBuilder PerGraph(this IDependencyBuilder source)
		{
			return source.Coalesce(d => d.Coalesce(lifestyle: new PerGraphLifestyle()));
		}

		public static IDependencyBuilder Pooled(this IDependencyBuilder source, int poolSize, int timeout)
		{
			return source.Coalesce(d => d.Coalesce(lifestyle: new PooledLifestyle(poolSize, timeout)));
		}

		public static IEnumerable<Func<IDependencyContainer, object>> Factories(this IDependencyBuilder source)
		{
			if (source == null)
			{
				throw new ArgumentNullException("source");
			}

			return source.Dependencies.Select(d => (Func<IDependencyContainer, object>)(c => c.Resolve(d.Key)));
		}

		public static Func<IDependencyContainer, object> Factory(this IDependencyBuilder source)
		{
			return Factories(source).Last();
		}

		public static IDependencyBuilder Parameter(this IDependencyBuilder source, string name, object value)
		{
			IDependencyParameter parameter = new DependencyParameter(c => value, p => String.Equals(p.Name, name, StringComparison.Ordinal));

			return source.Coalesce(d => d.Coalesce(parameters: d.Parameters.Concat(new IDependencyParameter[] { parameter })));
		}

		public static IDependencyBuilder Parameter<TType>(this IDependencyBuilder source, object value)
		{
			IDependencyParameter parameter = new DependencyParameter(c => value, p => p.ParameterType.IsAssignableFrom(typeof(TType)));

			return source.Coalesce(d => d.Coalesce(parameters: d.Parameters.Concat(new IDependencyParameter[] { parameter })));
		}

		private static IDependencyBuilder Coalesce(this IDependencyBuilder source, Func<IDependency, IDependency> decorator)
		{
			source.Decorate(e => e.Select((d => decorator(d))));

			return source;
		}

		public static IDependencyBuilder AddType(this IDependencyBuilder source, Type type)
		{
			if (source == null)
			{
				throw new ArgumentNullException("source");
			}

			return source.AddBuilder(FromType(type));
		}

		public static IDependencyBuilder AddType<TType>(this IDependencyBuilder source)
		{
			return AddType(source, typeof(TType));
		}

		public static IDependencyBuilder AddType<TType>(this IDependencyBuilder source, Func<IDependencyContainer, TType> factory)
		{
			return AddType<TType>(source, (c, d) => factory(c));
		}

		public static IDependencyBuilder AddType<TType>(this IDependencyBuilder source, Func<IDependencyContainer, IDependency, TType> factory)
		{
			if (source == null)
			{
				throw new ArgumentNullException("source");
			}

			return source.AddBuilder(FromType(typeof(TType), (c, d) => factory(c, d)));
		}

		public static IDependencyBuilder AddTypes(this IDependencyBuilder source, IEnumerable<Type> types)
		{
			if (source == null)
			{
				throw new ArgumentNullException("source");
			}

			return source.AddBuilder(types.SelectMany(t => FromType(t)));
		}

		public static IDependencyBuilder AddAmbientTypes(this IDependencyBuilder source)
		{
			return AddAmbientTypes(source, type => true);
		}

		public static IDependencyBuilder AddAmbientTypes(this IDependencyBuilder source, Func<Type, bool> predicate)
		{
			return AddTypes(source, AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes().Where(predicate)));
		}

		public static IDependencyBuilder AddAmbientTypesImplementing<TType>(this IDependencyBuilder source)
		{
			Type typeToFind = typeof(TType);

			return AddAmbientTypes(source, t => typeToFind.IsAssignableFrom(t));
		}
 
		private static IEnumerable<IDependency> FromType(Type type)
		{
			return FromType(type, (c, d) => c.Composer.Compose(c, type, d.Parameters));
		}

		private static IEnumerable<IDependency> FromType(Type type, Func<IDependencyContainer, IDependency, object> factory)
		{
			yield return new Dependency(new DependencyKey(type), factory, new PerGraphLifestyle(), Enumerable.Empty<IDependencyParameter>());
		}
	}
}
